﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Threading;

namespace XchangeStreamer.OutputSdk.Collections
{
    [Serializable]
    public class ReadOnlyObservableCollection<T> : IList<T>, IBindingList
    {
        #region Private Fields

        private SortableBindingList<T> innerList;
        private static readonly SynchronizationContext synchronizationContext;
        
        #endregion

        #region Public Events

        public event ListChangedEventHandler ListChanged;

        #endregion

        #region Constructors

        static ReadOnlyObservableCollection()
        {
            synchronizationContext = new System.Windows.Forms.WindowsFormsSynchronizationContext();
        }

        private ReadOnlyObservableCollection()
        { }

        public ReadOnlyObservableCollection(ObservableCollection<T> list)
        {
            this.innerList = new SortableBindingList<T>();

            foreach (T item in list)
            {
                this.innerList.Add(item);
            }

            ((INotifyCollectionChanged)list).CollectionChanged += new NotifyCollectionChangedEventHandler(this.HandleCollectionChanged);
            this.innerList.ListChanged += this.OnListChanged;
        }

        #endregion

        #region Public Properties

        public bool RaisesItemChangedEvents
        {
            get { return true; }
        }

        #endregion

        #region Private Methods

        private void HandleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:

                    if (this.innerList.IsSortEnabled)
                    {
                        this.innerList.RaiseListChangedEvents = false;
                    }

                    foreach (var item in e.NewItems)
                    {
                        this.innerList.Add((T)item);
                    }

                    if (!this.innerList.RaiseListChangedEvents)
                    {
                        this.innerList.RaiseListChangedEvents = true;
                    }

                    this.innerList.RefreshSort();
                    break;
                case NotifyCollectionChangedAction.Move:
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (var item in e.OldItems)
                    {
                        this.innerList.Remove((T)item);
                    }
                    //this.innerList.RefreshSort();
                    break;
                case NotifyCollectionChangedAction.Replace:
                    if (this.innerList.IsSortEnabled)
                    {
                        this.innerList.RaiseListChangedEvents = false;
                    }

                    for (int i = e.OldStartingIndex; i < e.OldStartingIndex + e.OldItems.Count; i++)
                    {
                        this.innerList.ResetItem(i);
                    }

                    if (!this.innerList.RaiseListChangedEvents)
                    {
                        this.innerList.RaiseListChangedEvents = true;
                    }

                    this.innerList.RefreshSort();
                    break;
                case NotifyCollectionChangedAction.Reset:
                    this.innerList.ResetBindings();
                    //this.innerList.RefreshSort();
                    break;
                default:
                    break;
            }
        }

        private void OnListChanged(object sender, ListChangedEventArgs e)
        {
            var eh = this.ListChanged;
            if (eh != null)
            {
                this.InternalOnListChanged(e, eh);
            }
        }

        private void InternalOnListChanged(ListChangedEventArgs e, ListChangedEventHandler eh)
        {
            System.Windows.Forms.CurrencyManager currencyManager;
            foreach (ListChangedEventHandler nh in eh.GetInvocationList())
            {
                currencyManager = nh.Target as System.Windows.Forms.CurrencyManager;
                if (currencyManager != null)
                {
                    synchronizationContext.Send(delegate
                    {
                        nh(this, e);
                    }, null);
                }
                else
                {
                    nh(this, e);
                }
            }
        }

        #endregion

        #region IBindingList Members

        public void AddIndex(PropertyDescriptor property)
        {
            ((IBindingList)this.innerList).AddIndex(property);
        }

        public object AddNew()
        {
            throw new NotImplementedException("ReadOnlyObservableCollection!");
        }

        public bool AllowEdit
        {
            get { return false; }
        }

        public bool AllowNew
        {
            get { return false; }
        }

        public bool AllowRemove
        {
            get { return false; }
        }

        public void ApplySort(PropertyDescriptor property, ListSortDirection direction)
        {
            ((IBindingList)this.innerList).ApplySort(property, direction);
        }

        public int Find(PropertyDescriptor property, object key)
        {
            return ((IBindingList)this.innerList).Find(property, key);
        }

        public bool IsSorted
        {
            get { return ((IBindingList)this.innerList).IsSorted; }
        }

        public void RemoveIndex(PropertyDescriptor property)
        {
            ((IBindingList)this.innerList).RemoveIndex(property);
        }

        public void RemoveSort()
        {
            ((IBindingList)this.innerList).RemoveSort();
        }

        public ListSortDirection SortDirection
        {
            get { return ((IBindingList)this.innerList).SortDirection; }
        }

        public PropertyDescriptor SortProperty
        {
            get { return ((IBindingList)this.innerList).SortProperty; }
        }

        public bool SupportsChangeNotification
        {
            get { return ((IBindingList)this.innerList).SupportsChangeNotification; }
        }

        public bool SupportsSearching
        {
            get { return ((IBindingList)this.innerList).SupportsSearching; }
        }

        public bool SupportsSorting
        {
            get
            {
                return true;
                //return ((IBindingList)this.innerList).SupportsSorting; 
            }
        }

        #endregion

        #region IList Members

        public int Add(object value)
        {
            throw new NotImplementedException("ReadOnlyObservableCollection!");
        }

        public void Clear()
        {
            throw new NotImplementedException("ReadOnlyObservableCollection!");
        }

        public bool Contains(object value)
        {
            return this.innerList.Contains((T)value);
        }

        public int IndexOf(object value)
        {
            return this.innerList.IndexOf((T)value);
        }

        public void Insert(int index, object value)
        {
            throw new NotImplementedException("ReadOnlyObservableCollection!");
        }

        public bool IsFixedSize
        {
            get { return true; }
        }

        public bool IsReadOnly
        {
            get { return true; }
        }

        public void Remove(object value)
        {
            throw new NotImplementedException("ReadOnlyObservableCollection!");
        }

        public void RemoveAt(int index)
        {
            //throw new NotImplementedException("ReadOnlyObservableCollection!");
            this.innerList.RemoveAt(index);
        }

        public object this[int index]
        {
            get { return this.innerList[index]; }
            set { throw new NotImplementedException("ReadOnlyObservableCollection!"); }
        }

        #endregion

        #region ICollection Members

        public void CopyTo(Array array, int index)
        {
            this.innerList.CopyTo((T[])array, index);
        }

        public int Count
        {
            get { return this.innerList.Count; }
        }

        public bool IsSynchronized
        {
            get { return ((ICollection)this.innerList).IsSynchronized; }
        }

        public object SyncRoot
        {
            get { return ((ICollection)this.innerList).SyncRoot; }
        }

        #endregion

        #region IEnumerable Members

        public IEnumerator GetEnumerator()
        {
            return this.innerList.GetEnumerator();
        }

        #endregion

        #region IList<T> Members

        public int IndexOf(T item)
        {
            return this.innerList.IndexOf(item);
        }

        public void Insert(int index, T item)
        {
            throw new NotImplementedException("ReadOnlyObservableCollection!");
        }

        T IList<T>.this[int index]
        {
            get { return (T)this.innerList[index]; }
            set { throw new NotImplementedException("ReadOnlyObservableCollection!"); }
        }

        #endregion

        #region ICollection<T> Members

        public void Add(T item)
        {
            throw new NotImplementedException("ReadOnlyObservableCollection!");
        }

        public bool Contains(T item)
        {
            return this.innerList.Contains(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            this.innerList.CopyTo(array, arrayIndex);
        }

        public bool Remove(T item)
        {
            throw new NotImplementedException("ReadOnlyObservableCollection!");
        }

        #endregion

        #region IEnumerable<T> Members

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return (IEnumerator<T>)this.innerList.GetEnumerator();
        }

        #endregion
    }

    /// <summary>
    /// Provides a generic collection that supports data binding and additionally supports sorting.
    /// See http://msdn.microsoft.com/en-us/library/ms993236.aspx
    /// If the elements are IComparable it uses that; otherwise compares the ToString()
    /// </summary>
    /// <typeparam name="T">The type of elements in the list.</typeparam>
    internal class SortableBindingList<T> : BindingList<T>
    {
        private bool _isSorted;
        private ListSortDirection _sortDirection = ListSortDirection.Ascending;
        private PropertyDescriptor _sortProperty;

        /// <summary>
        /// Gets a value indicating whether the list supports sorting.
        /// </summary>
        protected override bool SupportsSortingCore
        {
            get { return true; }
        }

        /// <summary>
        /// Gets a value indicating whether the list is sorted.
        /// </summary>
        protected override bool IsSortedCore
        {
            get { return _isSorted; }
        }

        /// <summary>
        /// Gets the direction the list is sorted.
        /// </summary>
        protected override ListSortDirection SortDirectionCore
        {
            get { return _sortDirection; }
        }

        /// <summary>
        /// Gets the property descriptor that is used for sorting the list if sorting is implemented in a derived class; otherwise, returns null
        /// </summary>
        protected override PropertyDescriptor SortPropertyCore
        {
            get { return _sortProperty; }
        }

        /// <summary>
        /// Removes any sort applied with ApplySortCore if sorting is implemented
        /// </summary>
        protected override void RemoveSortCore()
        {
            _sortDirection = ListSortDirection.Ascending;
            _sortProperty = null;
            this._isSorted = false;
        }

        /// <summary>
        /// Sorts the items if overridden in a derived class
        /// </summary>
        /// <param name="prop"></param>
        /// <param name="direction"></param>
        protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
        {
            _sortProperty = prop;
            _sortDirection = direction;

            this.InternalSort();
        }

        private void InternalSort()
        {
            List<T> list = Items as List<T>;
            if (list == null)
            {
                return;
            }

            list.Sort(Compare);

            _isSorted = true;
            //fire an event that the list has been changed.
            OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
        }

        private int Compare(T lhs, T rhs)
        {
            var result = OnComparison(lhs, rhs);
            //invert if descending
            if (_sortDirection == ListSortDirection.Descending)
                result = -result;
            return result;
        }

        private int OnComparison(T lhs, T rhs)
        {
            object lhsValue = lhs == null ? null : _sortProperty.GetValue(lhs);
            object rhsValue = rhs == null ? null : _sortProperty.GetValue(rhs);
            if (lhsValue == null)
            {
                return (rhsValue == null) ? 0 : -1; //nulls are equal
            }
            if (rhsValue == null)
            {
                return 1; //first has value, second doesn't
            }
            if (lhsValue is IComparable)
            {
                return ((IComparable)lhsValue).CompareTo(rhsValue);
            }
            if (lhsValue.Equals(rhsValue))
            {
                return 0; //both are the same
            }
            //not comparable, compare ToString
            return lhsValue.ToString().CompareTo(rhsValue.ToString());
        }

        public void RefreshSort()
        {
            if (this._isSorted && (this._sortProperty != null))
            {
                this.InternalSort();
            }
        }

        public bool IsSortEnabled
        {
            get
            {
                return this._isSorted;
            }
        }
    }
}